<template>
    <MyArticle class="readingNotes-webgl-01" title="WebGL学习笔记之一：概述与入门" date="2020年12月20日">
        <div class="_article-content">
            <h3 class="title">概述</h3>
            <p>WebGL是一项在网页上绘制和渲染三维图形，并可以和用户进行交互的技术。WebGL是内嵌在浏览器中，不必安装插件和库就可以直接使用它。其技术规范继承自免费和开源的OpenGL标准。支持<strong>可编程着色器方法</strong>，一种类似于C的编程语言并实现了精美的视觉效果。所以WebGL页面包含三种语言：<strong>HTML5、JavaScript、GLSL ES（着色器语言）</strong>。</p>
            <h3 class="title">清空绘图区</h3>
            <p>WebGL也是在canvas元素中绘制三维图形：</p>
            <pre><code>&lt;canvas ref="canvasRef" class="webgl-canvas"&gt;&lt;/canvas&gt;</code></pre>
            <pre><code>setup () {
    const canvasRef = ref(null)
    onMounted(() => {
        const gl = canvasRef.value.getContext('webgl2')
        gl.clearColor(0.0, 0.0, 0.0, 1.0) // 指定背景颜色
        gl.clear(gl.COLOR_BUFFER_BIT)
    })
    return { canvasRef }
}</code></pre>
            <canvas ref="canvasRef" class="webgl-canvas"></canvas>
            <p>由于WebGL是继承自OpenGL，所以它遵循传统的OpenGL颜色分量的取值范围，即从0.0~1.0，一旦指定了背景色之后，背景色就会驻存在WebGL系统中，在下一次调用gl.clearColor()方法之前是不会改变的。清空颜色缓冲区将导致WebGL清空页面上的canvas区域。</p>
            <pre><code>gl.clear(buffer) // 将指定缓冲区设定为预定的值</code></pre>
            <p>buffer值为<strong>gl.COLOR_BUFFER_BIT</strong>（颜色缓冲区）、<strong>gl.DEPTH_BUFFER_BIT</strong>(深度缓冲区)、<strong>gl.STENCIL_BUFFER_BIT</strong>（模板缓冲区）之一或者使用位操作符（|）指定多个。</p>
            <h3 class="title">绘制一个点</h3>
            <p>WebGL依赖于一种新的称为<strong>着色器（shader）</strong>的绘图机制。着色器提供了灵活且强大的绘制二维或三维图形的方法，所有WebGL程序必须使用它。</p>
            <canvas ref="canvasRef2" class="webgl-canvas"></canvas>
            <pre><code>const canvasRef2 = ref(null)
onMounted(() => {
    const el = canvasRef2.value
    const styles = window.getComputedStyle(el, null)
    el.width = parseInt(styles.width)
    el.height = parseInt(styles.height)
    const gl = el.getContext('webgl2')

    // 顶点着色器
    const VSHADER_SOURCE = `
        void main() {
            gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
            gl_PointSize = 10.0;
        }
    `
    // 片元着色器
    const FSHADER_SOURCE = `
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('init shader error')
        return
    }
    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clear(gl.COLOR_BUFFER_BIT)
    gl.drawArrays(gl.POINTS, 0, 1)
})</code></pre>
            <p><strong>着色器程序是以字符串的形式“嵌入”在JavaScript文件中的，在程序真正开始运行之前它就已经设置好了。</strong></p>
            <p><strong>顶点着色器（Vertex shader）</strong>：用来描述顶点特性（位置、颜色）的程序；<strong>片元着色器（Fragment shader）</strong>：进行逐片元处理过程如光照的程序。片元着色器接收到的是经过光栅化处理后的片元值。gl_Position和gl_PointSize这两个变量是内置在顶点着色器里面的。gl_FragColor变量是片元着色器唯一的内置变量，它控制着像素在屏幕上的最终颜色。</p>
            <pre><code>gl.drawArrays(mode, first, count)
// mode取值gl.POINTS、gl.LINES、gl.LINE_STRIP、gl.LINE_LOOP、gl.TRIANGLES、gl.TRIANGLE_STRIP、gl.TRIANGLE_FAN
// first指定从哪个顶点开始绘制
// count指定绘制需要用到多少个顶点</code></pre>
            <p><strong>WebGL程序包括运行在浏览器中的JavaScript和运行在WebG系统中的着色器程序这两部分。</strong></p>
            <p>GLSL ES是一种强类型的编程语言。开发者需要明确指出某个变量的某种类型。</p>
            <p>WebGL为右手坐标系，X轴水平的（正方向为右）；Y轴垂直的（正方向为下）；Z轴垂直于屏幕（正方向为外）。</p>
            <h3 class="title">使用attribute变量</h3>
            <p>实现将位置信息从JavaScript传递给顶点着色器，<strong>attribute变量</strong>和<strong>uniform变量</strong>可以做到。attribute变量传输那些与顶点相关的数据，而uniform变量则传递那些对于所有顶点都相同（或者与顶点无关）的数据。</p>
            <p>attribute变量是一种GLSL ES变量，被用来从外部向顶点着色器传输数据，只有顶点着色器能使用它。使用步骤如下：</p>
            <pre><code>1、在顶点着色器中，声明attribute变量；
2、将attribute变量赋值给gl_Position变量；
3、向attribute变量传输数据；</code></pre>
            <canvas ref="canvasRef3" class="webgl-canvas"></canvas>
            <pre><code>const canvasRef = ref(null)
onMounted(() => {
    const el = canvasRef.value
    const styles = window.getComputedStyle(el, null)
    el.width = parseInt(styles.width)
    el.height = parseInt(styles.height)
    const gl = el.getContext('webgl2')

    const VSHADER_SOURCE = ` // 顶点着色器
        attribute vec4 a_Position;
        void main() {
            gl_Position = a_Position;
            gl_PointSize = 10.0;
        }`
    const FSHADER_SOURCE = ` // 片元着色器
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }`
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('initShaders error')
    }
    const aPosition = gl.getAttribLocation(gl.program, 'a_Position') // 获取attribute变量的存储地址
    if (aPosition &lt; 0) {
        console.log('getAttribLocation error...')
        return
    }
    gl.vertexAttrib3f(aPosition, 0.5, 0.0, 0.0) // 传递顶点位置
    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clear(gl.COLOR_BUFFER_BIT)
    gl.drawArrays(gl.POINTS, 0, 1)
})</code></pre>
            <p>gl.vertexAttrib3f()是一系列同族函数中的一个，该系列函数的任务就是从JavaScript向顶点着色器中的attribute变量传值：</p>
            <pre><code>gl.vertexAttrib1f(location, v0)
gl.vertexAttrib2f(location, v0, v1)
gl.vertexAttrib3f(location, v0, v1, v2)
gl.vertexAttrib4f(location, v0, v1, v2, v3)</code></pre>
            <h3 class="title">通过点击绘制点</h3>
            <canvas ref="canvasRef4" class="webgl-canvas ref4-canvas"></canvas>
            <pre><code>const canvasRef4 = ref(null)
onMounted(() => {
    const el = canvasRef4.value
    const styles = window.getComputedStyle(el, null)
    el.width = parseInt(styles.width)
    el.height = parseInt(styles.height)
    const gl = el.getContext('webgl2')

    const VSHADER_SOURCE = `
        attribute vec4 a_Position;
        void main() {
            gl_Position = a_Position;
            gl_PointSize = 10.0;
        }`
    const FSHADER_SOURCE = `
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }`
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('initShaders error')
    }
    const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
    if (aPosition &lt; 0) {
        console.log('getAttribLocation error...')
        return
    }
    const points = []
    el.addEventListener('click', e => {
        let x = e.clientX
        let y = e.clientY
        const rect = el.getBoundingClientRect()
        x = ((x - rect.left) - el.width / 2) / (el.width / 2) // 转换坐标
        y = (el.height / 2 - (y - rect.top)) / (el.height / 2)
        points.push({ x, y })
        gl.clear(gl.COLOR_BUFFER_BIT)
        const len = points.length
        for (let i = 0; i &lt; len; i++) {
            gl.vertexAttrib3f(aPosition, points[i].x, points[i].y, 0.0)
            gl.drawArrays(gl.POINTS, 0, 1)
        }
    })
    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clear(gl.COLOR_BUFFER_BIT)
})</code></pre>
            <p>WebGl系统中的绘制操作实际上是在颜色缓冲区中进行绘制的，绘制结束之后系统将缓冲区中的内容显示在屏幕上，然后颜色缓冲区会被重置，其中的内容会丢失。</p>
            <h3 class="title">改变点的颜色</h3>
            <p>可以使用uniform变量将颜色值传给片元着色器，以实现点的颜色的动态改变。</p>
            <p>在片元着色器中准备uniform变量；用这个uniform变量向gl_FragColor赋值；将颜色值从JavaScript传给该uniform变量。</p>
            <canvas ref="canvasRef5" class="webgl-canvas ref5-canvas"></canvas>
            <pre><code>const FSHADER_SOURCE = `
    precision mediump float;
    uniform vec4 u_FragColor;
    void main() {
        gl_FragColor = u_FragColor;
    }`
// 获取uniform变量的储存位置
const uFragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
if (uFragColor &lt; 0) {
    console.log('getUniformLocation error...')
    return
}</code></pre>
            <p>每次点击生成随机颜色值：</p>
            <pre><code>const points = []
let color = ''
el.addEventListener('click', e => {
    let x = e.clientX
    let y = e.clientY
    const rect = el.getBoundingClientRect()
    x = ((x - rect.left) - el.width / 2) / (el.width / 2)
    y = (el.height / 2 - (y - rect.top)) / (el.height / 2)
    color = [Math.random(), Math.random(), Math.random(), 1.0] // 随机颜色值
    points.push({ x, y, color })
    gl.clear(gl.COLOR_BUFFER_BIT)
    const len = points.length
    for (let i = 0; i &lt; len; i++) {
        gl.vertexAttrib3f(aPosition, points[i].x, points[i].y, 0.0)
        gl.uniform4f(uFragColor, ...points[i].color)
        gl.drawArrays(gl.POINTS, 0, 1)
    }
})
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)</code></pre>
        </div>
    </MyArticle>
</template>

<script>
import { onMounted, ref } from 'vue'
import { initShaders } from '@/lib/webgl'

export default {
    setup () {
        // =============================================清空绘图缓冲区
        const canvasRef = ref(null)
        onMounted(() => {
            const gl = canvasRef.value.getContext('webgl2')
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
        })

        // =============================================绘制一个点
        const canvasRef2 = ref(null)
        onMounted(() => {
            const el = canvasRef2.value
            const styles = window.getComputedStyle(el, null)
            el.width = parseInt(styles.width)
            el.height = parseInt(styles.height)
            const gl = el.getContext('webgl2')

            // 顶点着色器
            const VSHADER_SOURCE = `
                void main() {
                    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
                    gl_PointSize = 10.0;
                }
            `
            const FSHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                }
            `
            if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
                console.log('init shader error')
                return
            }
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
            gl.drawArrays(gl.POINTS, 0, 1)
        })

        // =============================================通过attribute变量传输点的位置
        const canvasRef3 = ref(null)
        onMounted(() => {
            const el = canvasRef3.value
            const styles = window.getComputedStyle(el, null)
            el.width = parseInt(styles.width)
            el.height = parseInt(styles.height)
            const gl = el.getContext('webgl2')
            const VSHADER_SOURCE = `
                attribute vec4 a_Position;
                void main() {
                    gl_Position = a_Position;
                    gl_PointSize = 10.0;
                }`
            const FSHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                }`
            if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
                console.log('initShaders error')
            }
            const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
            if (aPosition < 0) {
                console.log('getAttribLocation error...')
                return
            }
            gl.vertexAttrib3f(aPosition, 0.5, 0.0, 0.0)
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
            gl.drawArrays(gl.POINTS, 0, 1)
        })

        // =============================================通过点击绘制点
        const canvasRef4 = ref(null)
        onMounted(() => {
            const el = canvasRef4.value
            const styles = window.getComputedStyle(el, null)
            el.width = parseInt(styles.width)
            el.height = parseInt(styles.height)
            const gl = el.getContext('webgl2')

            const VSHADER_SOURCE = `
                attribute vec4 a_Position;
                void main() {
                    gl_Position = a_Position;
                    gl_PointSize = 10.0;
                }`
            const FSHADER_SOURCE = `
                void main() {
                    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                }`
            if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
                console.log('initShaders error')
            }
            const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
            if (aPosition < 0) {
                console.log('getAttribLocation error...')
                return
            }
            const points = []
            el.addEventListener('click', e => {
                let x = e.clientX
                let y = e.clientY
                const rect = el.getBoundingClientRect()
                x = ((x - rect.left) - el.width / 2) / (el.width / 2)
                y = (el.height / 2 - (y - rect.top)) / (el.height / 2)
                points.push({ x, y })
                gl.clear(gl.COLOR_BUFFER_BIT)
                const len = points.length
                for (let i = 0; i < len; i++) {
                    gl.vertexAttrib3f(aPosition, points[i].x, points[i].y, 0.0)
                    gl.drawArrays(gl.POINTS, 0, 1)
                }
            })
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
        })

        // =============================================动态改变点的颜色
        const canvasRef5 = ref(null)
        onMounted(() => {
            const el = canvasRef5.value
            const styles = window.getComputedStyle(el, null)
            el.width = parseInt(styles.width)
            el.height = parseInt(styles.height)
            const gl = el.getContext('webgl2')

            const VSHADER_SOURCE = `
                attribute vec4 a_Position;
                void main() {
                    gl_Position = a_Position;
                    gl_PointSize = 10.0;
                }`
            const FSHADER_SOURCE = `
                precision mediump float;
                uniform vec4 u_FragColor;
                void main() {
                    gl_FragColor = u_FragColor;
                }`
            if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
                console.log('initShaders error')
            }
            const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
            if (aPosition < 0) {
                console.log('getAttribLocation error...')
                return
            }
            const uFragColor = gl.getUniformLocation(gl.program, 'u_FragColor')
            if (uFragColor < 0) {
                console.log('getUniformLocation error...')
                return
            }
            const points = []
            let color = ''
            el.addEventListener('click', e => {
                let x = e.clientX
                let y = e.clientY
                const rect = el.getBoundingClientRect()
                x = ((x - rect.left) - el.width / 2) / (el.width / 2)
                y = (el.height / 2 - (y - rect.top)) / (el.height / 2)
                color = [Math.random(), Math.random(), Math.random(), 1.0]
                points.push({ x, y, color })
                gl.clear(gl.COLOR_BUFFER_BIT)
                const len = points.length
                for (let i = 0; i < len; i++) {
                    gl.vertexAttrib3f(aPosition, points[i].x, points[i].y, 0.0)
                    gl.uniform4f(uFragColor, ...points[i].color)
                    gl.drawArrays(gl.POINTS, 0, 1)
                }
            })
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
        })
        return { canvasRef, canvasRef2, canvasRef3, canvasRef4, canvasRef5 }
    }
}
</script>

<style lang="scss">
    @use './style.scss';
</style>
